package cn.melonlib.cores.graphql.comp.types;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.melonlib.cores.commons.model.entity.IEntity;
import cn.melonlib.cores.jpa.model.bean.collections.EntityMap;
import cn.melonlib.cores.jpa.model.bean.collections.JsonMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.language.*;
import graphql.schema.*;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StreamUtils;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Configuration
public class GraphQLTypes {

    private static final Pattern DATE_FMT = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$");
    private static final Pattern DATE_TIME_FMT = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$");
    private static final Pattern DATE_TIME_SS_FMT = Pattern.compile("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$");

    @Resource
    private ObjectMapper jsonMapper;

    @Bean
    public GraphQLScalarType dateType() {
        return GraphQLScalarType.newScalar().name("Date").description("Date Time Type")
                .coercing(new Coercing<Long, Long>() {
                    @Override
                    public Long serialize(Object input) throws CoercingSerializeException {
                        if (input instanceof Date) {
                            return ((Date) input).getTime();
                        }
                        if (input instanceof Number) {
                            return ((Number) input).longValue();
                        }
                        if (input instanceof String) {
                            try {
                                return DateUtils.parseDate(input.toString(), "yyyy-MM-dd").getTime();
                            } catch (ParseException e) {
                                throw new CoercingSerializeException(e.getMessage(), e);
                            }
                        }
                        return null;
                    }

                    @Override
                    public Long parseValue(Object input) throws CoercingParseValueException {
                        if (input instanceof Number) {
                            return ((Number) input).longValue();
                        }
                        if (input instanceof CharSequence) {
                            String value = input.toString();
                            try {
                                if (DATE_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd").getTime();
                                }
                                if (DATE_TIME_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd HH:mm").getTime();
                                }
                                if (DATE_TIME_SS_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd HH:mm:ss").getTime();
                                }
                            } catch (Exception e) {
                                throw new CoercingParseLiteralException(e.getMessage(), e);
                            }
                        }
                        throw new CoercingParseLiteralException("不能转化为时间戳");
                    }

                    @Override
                    public Long parseLiteral(Object input) throws CoercingParseLiteralException {
                        if (input instanceof IntValue) {
                            return ((IntValue) input).getValue().longValue();
                        } else if (input instanceof StringValue) {
                            String value = ((StringValue) input).getValue();
                            try {
                                if (DATE_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd").getTime();
                                }
                                if (DATE_TIME_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd HH:mm").getTime();
                                }
                                if (DATE_TIME_SS_FMT.matcher(value).matches()) {
                                    return DateUtils.parseDate(value, "yyyy-MM-dd HH:mm:ss").getTime();
                                }
                            } catch (Exception e) {
                                throw new CoercingParseLiteralException(e.getMessage(), e);
                            }
                        }
                        throw new CoercingParseLiteralException("不能转换为时间戳");

                    }
                })
                .build();
    }

    @Bean
    public GraphQLScalarType viewEntityType() {
        return GraphQLScalarType.newScalar().name("ViewEntity").description("View Type").coercing(new Coercing() {
            @Override
            public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
                return dataFetcherResult;
            }

            @Override
            public Object parseValue(Object input) throws CoercingParseValueException {
                throw new CoercingParseValueException("不支持输入方式");
            }

            @Override
            public Object parseLiteral(Object input) throws CoercingParseLiteralException {
                throw new CoercingParseValueException("不支持输入方式");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType filterType() {
        return GraphQLScalarType.newScalar().name("Filter").description("Filter Type").coercing(new Coercing<JsonMap<?>,Object>() {

            @Override
            public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
                throw new CoercingParseValueException("不支持输出方式");
            }

            @Override
            public JsonMap<?> parseValue(Object input) throws CoercingParseValueException {
                if (input instanceof Map) {
                    return JsonMap.createMap((Map<String, ? extends Object>) input);
                }
                if (input instanceof CharSequence) {
                    try {
                        return jsonMapper.readValue(input.toString(), new TypeReference<JsonMap<Object>>() {
                        });
                    } catch (Exception e) {
                        throw new CoercingParseValueException(e.getMessage(), e);
                    }
                }
                return JsonMap.createMap(BeanUtil.beanToMap(input));

            }

            @Override
            public JsonMap<?> parseLiteral(Object input) throws CoercingParseLiteralException {
                if (input instanceof StringValue) {
                    String value = ((StringValue) input).getValue();
                    try {
                        return jsonMapper.readValue(value, new TypeReference<JsonMap<Object>>() {
                        });
                    } catch (JsonProcessingException e) {
                        throw new CoercingParseLiteralException(e.getMessage(), e);
                    }
                }
                if (input instanceof ObjectValue) {
                    return JsonMap.createMap((Map)parseFieldValue((ObjectValue) input));
                }

                throw new CoercingParseLiteralException("不支持的输入类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType inputType() {
        return GraphQLScalarType.newScalar().name("InputEntity").description("Input Type").coercing(new Coercing() {

            @Override
            public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
                throw new CoercingParseValueException("不支持输出方式");
            }

            @Override
            public Object parseValue(Object input) throws CoercingParseValueException {
                if (input instanceof Map) {
                    return JsonMap.createMap((Map<String, ? extends Object>) input);
                }
                if(input instanceof String){
                    try {
                        return jsonMapper.readValue(input.toString(), new TypeReference<JsonMap<Object>>() {
                        });
                    } catch (Exception e) {
                        throw new CoercingParseValueException(e.getMessage(), e);
                    }
                }
                return JsonMap.createMap(BeanUtil.beanToMap(input));

            }

            @Override
            public Object parseLiteral(Object input) throws CoercingParseLiteralException {
                if (input instanceof StringValue) {
                    String value = ((StringValue) input).getValue();
                    try {
                        return jsonMapper.readValue(value, new TypeReference<JsonMap<Object>>() {
                        });
                    } catch (JsonProcessingException e) {
                        throw new CoercingParseLiteralException(e.getMessage(), e);
                    }
                }
                if (input instanceof ObjectValue) {
                    return parseFieldValue((ObjectValue) input);
                }
                throw new CoercingParseLiteralException("不支持的输入类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType listType() {
        return GraphQLScalarType.newScalar().name("ArrayList").description("List Type").coercing(new Coercing<List<?>,List<?>>() {
            @Override
            public List<?> serialize(Object dataFetcherResult) throws CoercingSerializeException {
                if(dataFetcherResult instanceof List){
                    return (List<?>) dataFetcherResult;
                }
                if(dataFetcherResult instanceof CharSequence){
                    try {
                        return jsonMapper.readValue(dataFetcherResult.toString(), new TypeReference<List<Map<String,Object>>>() {
                        });
                    }catch (Exception e){
                        return Arrays.stream(dataFetcherResult.toString().split(",")).collect(Collectors.toList());
                    }
                }
                throw new CoercingParseLiteralException("不支持的输出类型");
            }

            @Override
            public List<?> parseValue(Object input) throws CoercingParseValueException {
                if(input instanceof List){
                    return (List<?>) input;
                }
                if(input instanceof CharSequence){
                    try {
                        return jsonMapper.readValue(input.toString(), new TypeReference<List<Map<String,Object>>>() {
                        });
                    }catch (Exception e){
                        return Arrays.stream(input.toString().split(",")).collect(Collectors.toList());
                    }
                }
                throw new CoercingParseLiteralException("不支持的输出类型");
            }

            @Override
            public List<?> parseLiteral(Object input) throws CoercingParseLiteralException {
                if(input instanceof ArrayValue){
                    return ((ArrayValue) input).getValues();
                }
                if(input instanceof StringValue){
                    try {
                        return jsonMapper.readValue(((StringValue) input).getValue(), new TypeReference<List<Map<String,Object>>>() {
                        });
                    }catch (Exception e){
                        return Arrays.stream(((StringValue) input).getValue().split(",")).collect(Collectors.toList());
                    }
                }
                throw new CoercingParseLiteralException("不支持的输出类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType byteDataType(){
        return GraphQLScalarType.newScalar().name("ByteData").description("Bytes").coercing(new Coercing<byte[],String>() {
            @Override
            public String serialize(Object data) throws CoercingSerializeException {
                if(data instanceof byte[]){
                    return Base64.encodeUrlSafe((byte[])data);
                }
                if(data instanceof String){
                    return data.toString();
                }
                if(data instanceof File){
                    return Base64.encodeUrlSafe(FileUtil.readBytes((File) data));
                }
                if(data instanceof InputStream){
                    try {
                        return Base64.encodeUrlSafe(StreamUtils.copyToByteArray((InputStream) data));
                    } catch (IOException e) {
                        throw new CoercingSerializeException(e.getMessage(),e);
                    }
                }
                throw new CoercingSerializeException("不支持的输出类型");
            }

            @Override
            public byte[] parseValue(Object input) throws CoercingParseValueException {
                if(input instanceof CharSequence){
                    return Base64.decode(input.toString());
                }
                throw new CoercingSerializeException("不支持的输入类型");
            }

            @Override
            public byte[] parseLiteral(Object input) throws CoercingParseLiteralException {
                if(input instanceof StringValue){
                    return Base64.decode(((StringValue) input).getValue());
                }
                throw new CoercingSerializeException("不支持的输入类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType fileType(){
        return GraphQLScalarType.newScalar().name("File").description("File Type").coercing(new Coercing<File,String>() {
            @Override
            public String serialize(Object data) throws CoercingSerializeException {
                try {
                    return Base64.encodeUrlSafe(FileUtil.readBytes((File) data));
                }catch (Exception e){
                    throw new CoercingSerializeException(e.getMessage(),e);
                }
            }

            @Override
            public File parseValue(Object input) throws CoercingParseValueException {
                throw new CoercingSerializeException("不支持的输入类型");
            }

            @Override
            public File parseLiteral(Object input) throws CoercingParseLiteralException {
                throw new CoercingSerializeException("不支持的输入类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType errorType(){
        return GraphQLScalarType.newScalar().name("Error").description("Error Information").coercing(new Coercing() {
            @Override
            public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
                if(dataFetcherResult instanceof IEntity){
                    return new EntityMap((IEntity<? extends Serializable>) dataFetcherResult);
                }else{
                    return dataFetcherResult;
                }
            }

            @Override
            public Object parseValue(Object input) throws CoercingParseValueException {
                throw new CoercingSerializeException("不支持的输入类型");
            }

            @Override
            public Object parseLiteral(Object input) throws CoercingParseLiteralException {
                throw new CoercingSerializeException("不支持的输入类型");
            }
        }).build();
    }

    @Bean
    public GraphQLScalarType messageType(){

        return GraphQLScalarType.newScalar().name("Message").description("Message Information").coercing(new Coercing() {
            @Override
            public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
                if(dataFetcherResult instanceof IEntity){
                    return new EntityMap<>((IEntity<? extends Serializable>) dataFetcherResult);
                }else{
                    return dataFetcherResult;
                }
            }

            @Override
            public Object parseValue(Object input) throws CoercingParseValueException {
                throw new CoercingSerializeException("不支持的输入类型");
            }

            @Override
            public Object parseLiteral(Object input) throws CoercingParseLiteralException {
                throw new CoercingSerializeException("不支持的输入类型");
            }
        }).build();
    }

    private static Object parseFieldValue(Value value) {
        if (value instanceof StringValue) {
            return ((StringValue) value).getValue();
        }
        if (value instanceof IntValue) {
            return ((IntValue) value).getValue();
        }
        if (value instanceof FloatValue) {
            return ((FloatValue) value).getValue();
        }
        if (value instanceof BooleanValue) {
            return ((BooleanValue) value).isValue();
        }
        if (value instanceof EnumValue) {
            return ((EnumValue) value).getName();
        }
        if (value instanceof NullValue) {
            return null;
        }
        if (value instanceof ArrayValue) {
            return ((ArrayValue) value).getValues().stream()
                    .map(GraphQLTypes::parseFieldValue)
                    .collect(Collectors.toList());
        }
        if (value instanceof ObjectValue) {
            return ((ObjectValue) value).getObjectFields().stream()
                    .collect(Collectors.toMap(ObjectField::getName, field -> parseFieldValue(field.getValue()), (value1, value2) -> value2));
        }
        if (value instanceof VariableReference) {
            return ((ObjectValue) value).getObjectFields().stream()
                    .collect(Collectors.toMap(ObjectField::getName, field -> parseFieldValue(field.getValue()), (value1, value2) -> value2));
        }
        throw new IllegalArgumentException("Unsupported scalar value type: " + value.getClass().getName());
    }
}
